#include "astyle.h"

namespace astyle {
  ASEnhancer::ASEnhancer() {
  }

  ASEnhancer::~ASEnhancer() {
  }
  void ASEnhancer::init( int  _fileType,
                         int  _indentLength,
                         int  _tabLength,
                         bool _useTabs,
                         bool _forceTab,
                         bool _namespaceIndent,
                         bool _caseIndent,
                         bool _preprocBlockIndent,
                         bool _preprocDefineIndent,
                         bool _emptyLineFill,
                         vector<const pair<const string, const string>* >* _indentableMacros ) {
    ASBase::init( _fileType );
    indentLength = _indentLength;
    tabLength = _tabLength;
    useTabs = _useTabs;
    forceTab = _forceTab;
    namespaceIndent = _namespaceIndent;
    caseIndent = _caseIndent;
    preprocBlockIndent = _preprocBlockIndent;
    preprocDefineIndent = _preprocDefineIndent;
    emptyLineFill = _emptyLineFill;
    indentableMacros = _indentableMacros;
    quoteChar = '\'';
    lineNumber = 0;
    braceCount = 0;
    isInComment = false;
    isInQuote = false;
    switchDepth = 0;
    eventPreprocDepth = 0;
    lookingForCaseBrace = false;
    unindentNextLine = false;
    shouldUnindentLine = false;
    shouldUnindentComment = false;
    sw.switchBraceCount = 0;
    sw.unindentDepth = 0;
    sw.unindentCase = false;
    switchStack.clear();
    nextLineIsEventIndent = false;
    isInEventTable = false;
    nextLineIsDeclareIndent = false;
    isInDeclareSection = false;
  }


  void ASEnhancer::enhance( string& line, bool isInNamespace, bool isInPreprocessor, bool isInSQL ) {
    shouldUnindentLine = true;
    shouldUnindentComment = false;
    lineNumber++;
    if( nextLineIsEventIndent ) {
      isInEventTable = true;
      nextLineIsEventIndent = false;
    }
    if( nextLineIsDeclareIndent ) {
      isInDeclareSection = true;
      nextLineIsDeclareIndent = false;
    }
    if( line.length() == 0
        && !isInEventTable
        && !isInDeclareSection
        && !emptyLineFill ) {
      return;
    }
    if( unindentNextLine ) {
      sw.unindentDepth++;
      sw.unindentCase = true;
      unindentNextLine = false;
    }
    parseCurrentLine( line, isInPreprocessor, isInSQL );
    if( isInDeclareSection ) {
      size_t firstText = line.find_first_not_of( " \t" );
      if( firstText == string::npos || line[firstText] != '#' ) {
        indentLine( line, 1 );
      }
    }
    if( isInEventTable
        && ( eventPreprocDepth == 0
             || ( namespaceIndent && isInNamespace ) ) ) {
      size_t firstText = line.find_first_not_of( " \t" );
      if( firstText == string::npos || line[firstText] != '#' ) {
        indentLine( line, 1 );
      }
    }
    if( shouldUnindentComment && sw.unindentDepth > 0 ) {
      unindentLine( line, sw.unindentDepth - 1 );
    } else if( shouldUnindentLine && sw.unindentDepth > 0 ) {
      unindentLine( line, sw.unindentDepth );
    }
  }


  void ASEnhancer::convertForceTabIndentToSpaces( string& line ) const {
    for( size_t i = 0; i < line.length(); i++ ) {
      if( !isWhiteSpace( line[i] ) ) {
        break;
      }
      if( line[i] == '\t' ) {
        line.erase( i, 1 );
        line.insert( i, tabLength, ' ' );
        i += tabLength - 1;
      }
    }
  }


  void ASEnhancer::convertSpaceIndentToForceTab( string& line ) const {
    assert( tabLength > 0 );
    size_t newSpaceIndentLength = line.find_first_not_of( " \t" );
    size_t tabCount = newSpaceIndentLength / tabLength;
    line.replace( 0U, tabCount * tabLength, tabCount, '\t' );
  }


  size_t ASEnhancer::findCaseColon( const string& line, size_t caseIndex ) const {
    size_t i = caseIndex;
    bool isInQuote_ = false;
    char quoteChar_ = ' ';
    for( ; i < line.length(); i++ ) {
      if( isInQuote_ ) {
        if( line[i] == '\\' ) {
          i++;
          continue;
        } else if( line[i] == quoteChar_ ) {
          isInQuote_ = false;
          quoteChar_ = ' ';
          continue;
        } else
        { continue; }
      }
      if( line[i] == '"'
          || ( line[i] == '\'' && !isDigitSeparator( line, i ) ) ) {
        isInQuote_ = true;
        quoteChar_ = line[i];
        continue;
      }
      if( line[i] == ':' ) {
        if( ( i + 1 < line.length() ) && ( line[i + 1] == ':' ) ) {
          i++;
        } else
        { break; }
      }
    }
    return i;
  }


  int ASEnhancer::indentLine( string& line, int indent ) const {
    if( line.length() == 0
        && !emptyLineFill ) {
      return 0;
    }
    size_t charsToInsert = 0;
    if( forceTab && indentLength != tabLength ) {
      convertForceTabIndentToSpaces( line );
      charsToInsert = indent * indentLength;
      line.insert( line.begin(), charsToInsert, ' ' );
      convertSpaceIndentToForceTab( line );
    } else if( useTabs ) {
      charsToInsert = indent;
      line.insert( line.begin(), charsToInsert, '\t' );
    } else {
      charsToInsert = indent * indentLength;
      line.insert( line.begin(), charsToInsert, ' ' );
    }
    return charsToInsert;
  }


  bool ASEnhancer::isBeginDeclareSectionSQL( const string& line, size_t index ) const {
    string word;
    size_t hits = 0;
    size_t i;
    for( i = index; i < line.length(); i++ ) {
      i = line.find_first_not_of( " \t", i );
      if( i == string::npos ) {
        return false;
      }
      if( line[i] == ';' ) {
        break;
      }
      if( !isCharPotentialHeader( line, i ) ) {
        continue;
      }
      word = getCurrentWord( line, i );
      for( size_t j = 0; j < word.length(); j++ ) {
        word[j] = ( char ) toupper( word[j] );
      }
      if( word == "EXEC" || word == "SQL" ) {
        i += word.length() - 1;
        continue;
      }
      if( word == "DECLARE" || word == "SECTION" ) {
        hits++;
        i += word.length() - 1;
        continue;
      }
      if( word == "BEGIN" ) {
        hits++;
        i += word.length() - 1;
        continue;
      }
      return false;
    }
    if( hits == 3 ) {
      return true;
    }
    return false;
  }


  bool ASEnhancer::isEndDeclareSectionSQL( const string& line, size_t index ) const {
    string word;
    size_t hits = 0;
    size_t i;
    for( i = index; i < line.length(); i++ ) {
      i = line.find_first_not_of( " \t", i );
      if( i == string::npos ) {
        return false;
      }
      if( line[i] == ';' ) {
        break;
      }
      if( !isCharPotentialHeader( line, i ) ) {
        continue;
      }
      word = getCurrentWord( line, i );
      for( size_t j = 0; j < word.length(); j++ ) {
        word[j] = ( char ) toupper( word[j] );
      }
      if( word == "EXEC" || word == "SQL" ) {
        i += word.length() - 1;
        continue;
      }
      if( word == "DECLARE" || word == "SECTION" ) {
        hits++;
        i += word.length() - 1;
        continue;
      }
      if( word == "END" ) {
        hits++;
        i += word.length() - 1;
        continue;
      }
      return false;
    }
    if( hits == 3 ) {
      return true;
    }
    return false;
  }


  bool ASEnhancer::isOneLineBlockReached( const string& line, int startChar ) const {
    assert( line[startChar] == '{' );
    bool isInComment_ = false;
    bool isInQuote_ = false;
    int _braceCount = 1;
    int lineLength = line.length();
    char quoteChar_ = ' ';
    char ch = ' ';
    for( int i = startChar + 1; i < lineLength; ++i ) {
      ch = line[i];
      if( isInComment_ ) {
        if( line.compare( i, 2, "*/" ) == 0 ) {
          isInComment_ = false;
          ++i;
        }
        continue;
      }
      if( ch == '\\' ) {
        ++i;
        continue;
      }
      if( isInQuote_ ) {
        if( ch == quoteChar_ ) {
          isInQuote_ = false;
        }
        continue;
      }
      if( ch == '"' || ( ch == '\'' && !isDigitSeparator( line, i ) ) ) {
        isInQuote_ = true;
        quoteChar_ = ch;
        continue;
      }
      if( line.compare( i, 2, "//" ) == 0 ) {
        break;
      }
      if( line.compare( i, 2, "*" ) == 0 ) {
        isInComment_ = true;
        ++i;
        continue;
      }
      if( ch == '{' ) {
        ++_braceCount;
      } else if( ch == '}' ) {
        --_braceCount;
      }
      if( _braceCount == 0 ) {
        return true;
      }
    }
    return false;
  }

  void ASEnhancer::parseCurrentLine( string& line, bool isInPreprocessor, bool isInSQL ) {
    bool isSpecialChar = false;
    for( size_t i = 0; i < line.length(); i++ ) {
      char ch = line[i];
      if( isWhiteSpace( ch ) ) {
        continue;
      }
      if( isSpecialChar ) {
        isSpecialChar = false;
        continue;
      }
      if( !( isInComment ) && line.compare( i, 2, "\\\\" ) == 0 ) {
        i++;
        continue;
      }
      if( !( isInComment ) && ch == '\\' ) {
        isSpecialChar = true;
        continue;
      }
      if( !isInComment && ( ch == '"' || ( ch == '\'' && !isDigitSeparator( line, i ) ) ) ) {
        if( !isInQuote ) {
          quoteChar = ch;
          isInQuote = true;
        } else if( quoteChar == ch ) {
          isInQuote = false;
          continue;
        }
      }
      if( isInQuote ) {
        continue;
      }
      if( !( isInComment ) && line.compare( i, 2, "//" ) == 0 ) {
        if( line.compare( i + 2, 1, "\xf0" ) > 0 ) {
          lineNumber--;
        }
        if( line.find_first_not_of( " \t" ) == i
            && sw.switchBraceCount == 1
            && sw.unindentCase ) {
          shouldUnindentComment = true;
        }
        break;
      } else if( !( isInComment ) && line.compare( i, 2, "/*" ) == 0 ) {
        if( sw.switchBraceCount == 1 && sw.unindentCase ) {
          shouldUnindentComment = true;
        }
        isInComment = true;
        size_t commentEnd = line.find( "*/", i );
        if( commentEnd == string::npos ) {
          i = line.length() - 1;
        } else
        { i = commentEnd - 1; }
        continue;
      } else if( ( isInComment ) && line.compare( i, 2, "*/" ) == 0 ) {
        if( sw.switchBraceCount == 1 && sw.unindentCase ) {
          shouldUnindentComment = true;
        }
        isInComment = false;
        i++;
        continue;
      }
      if( isInComment ) {
        if( sw.switchBraceCount == 1 && sw.unindentCase ) {
          shouldUnindentComment = true;
        }
        size_t commentEnd = line.find( "*/", i );
        if( commentEnd == string::npos ) {
          i = line.length() - 1;
        } else
        { i = commentEnd - 1; }
        continue;
      }
      if( line[i] == '{' ) {
        braceCount++;
      }
      if( line[i] == '}' ) {
        braceCount--;
      }
      if( isInEventTable && line[i] == '#' && preprocBlockIndent ) {
        string preproc;
        preproc = line.substr( i + 1 );
        if( preproc.substr( 0, 2 ) == "if" )
          if( preproc.substr( 0, 5 ) == "endif" && eventPreprocDepth > 0 ) {
            eventPreprocDepth -= 1;
          }
      }
      bool isPotentialKeyword = isCharPotentialHeader( line, i );
      if( isPotentialKeyword ) {
        for( size_t j = 0; j < indentableMacros->size(); j++ ) {
          if( findKeyword( line, i, indentableMacros->at( j )->first ) ) {
            nextLineIsEventIndent = true;
            break;
          }
        }
        for( size_t j = 0; j < indentableMacros->size(); j++ ) {
          if( findKeyword( line, i, indentableMacros->at( j )->second ) ) {
            isInEventTable = false;
            eventPreprocDepth = 0;
            break;
          }
        }
      }
      if( isInSQL ) {
        if( isBeginDeclareSectionSQL( line, i ) ) {
          nextLineIsDeclareIndent = true;
        }
        if( isEndDeclareSectionSQL( line, i ) ) {
          isInDeclareSection = false;
        }
        break;
      }
      if( isPotentialKeyword && findKeyword( line, i, ASResource::AS_SWITCH ) ) {
        switchDepth++;
        switchStack.emplace_back( sw );
        sw.switchBraceCount = 0;
        sw.unindentCase = false;
        i += 5;
        continue;
      }
      if( caseIndent
          || switchDepth == 0
          || ( isInPreprocessor && !preprocDefineIndent ) ) {
        if( isPotentialKeyword ) {
          string name = getCurrentWord( line, i );
          i += name.length() - 1;
        }
        continue;
      }
      i = processSwitchBlock( line, i );
    }
  }


  size_t ASEnhancer::processSwitchBlock( string& line, size_t index ) {
    size_t i = index;
    bool isPotentialKeyword = isCharPotentialHeader( line, i );
    if( line[i] == '{' ) {
      sw.switchBraceCount++;
      if( lookingForCaseBrace ) {
        sw.unindentCase = true;
        sw.unindentDepth++;
        lookingForCaseBrace = false;
      }
      return i;
    }
    lookingForCaseBrace = false;
    if( line[i] == '}' ) {
      sw.switchBraceCount--;
      assert( sw.switchBraceCount <= braceCount );
      if( sw.switchBraceCount == 0 ) {
        int lineUnindent = sw.unindentDepth;
        if( line.find_first_not_of( " \t" ) == i
            && !switchStack.empty() ) {
          lineUnindent = switchStack[switchStack.size() - 1].unindentDepth;
        }
        if( shouldUnindentLine ) {
          if( lineUnindent > 0 ) {
            i -= unindentLine( line, lineUnindent );
          }
          shouldUnindentLine = false;
        }
        switchDepth--;
        sw = switchStack.back();
        switchStack.pop_back();
      }
      return i;
    }
    if( isPotentialKeyword
        && ( findKeyword( line, i, ASResource::AS_CASE )
             || findKeyword( line, i, ASResource::AS_DEFAULT ) ) ) {
      if( sw.unindentCase ) {
        sw.unindentCase = false;
        sw.unindentDepth--;
      }
      i = findCaseColon( line, i );
      i++;
      for( ; i < line.length(); i++ ) {
        if( !isWhiteSpace( line[i] ) ) {
          break;
        }
      }
      if( i < line.length() ) {
        if( line[i] == '{' ) {
          braceCount++;
          sw.switchBraceCount++;
          if( !isOneLineBlockReached( line, i ) ) {
            unindentNextLine = true;
          }
          return i;
        }
      }
      lookingForCaseBrace = true;
      i--;
      return i;
    }
    if( isPotentialKeyword ) {
      string name = getCurrentWord( line, i );
      i += name.length() - 1;
    }
    return i;
  }
  int ASEnhancer::unindentLine( string& line, int unindent ) const {
    size_t whitespace = line.find_first_not_of( " \t" );
    if( whitespace == string::npos ) {
      whitespace = line.length();
    }
    if( whitespace == 0 ) {
      return 0;
    }
    size_t charsToErase = 0;
    if( forceTab && indentLength != tabLength ) {
      convertForceTabIndentToSpaces( line );
      size_t spaceIndentLength = line.find_first_not_of( " \t" );
      charsToErase = unindent * indentLength;
      if( charsToErase <= spaceIndentLength ) {
        line.erase( 0, charsToErase );
      } else
      { charsToErase = 0; }
      convertSpaceIndentToForceTab( line );
    } else if( useTabs ) {
      charsToErase = unindent;
      if( charsToErase <= whitespace ) {
        line.erase( 0, charsToErase );
      } else
      { charsToErase = 0; }
    } else {
      charsToErase = unindent * indentLength;
      if( charsToErase <= whitespace ) {
        line.erase( 0, charsToErase );
      } else
      { charsToErase = 0; }
    }
    return charsToErase;
  }

}
